/*
 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *        * Redistributions of source code must retain the above copyright
 *            notice, this list of conditions and the following disclaimer.
 *        * Redistributions in binary form must reproduce the above copyright
 *            notice, this list of conditions and the following disclaimer in the
 *            documentation and/or other materials provided with the distribution.
 *        * Neither the name of The Linux Foundation nor
 *            the names of its contributors may be used to endorse or promote
 *            products derived from this software without specific prior written
 *            permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT ARE DISCLAIMED.    IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "ConfFileParser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <utils/Log.h>

//declaration of functions only specific to this file
static char parse_line
(
  group_table *key_file,
  const char *line,
  char **cur_grp
);

static char parse_load_frm_fhandler
(
  group_table *key_file,
  FILE *fp
);

static char line_is_grp
(
  group_table *key_file,
  const char *str,
  char **cur_grp
);

static void free_grp_list
(
  group *a
);

static void free_key_list
(
  key_value_pair_list *a
);

static char line_is_key_value_pair
(
  group_table *key_file,
  const char *str,
  const char *cur_grp
);

static char line_is_comment
(
  const char *str
);

static char grp_exist
(
  const group_table *key_file,
  const char *new_grp
);

static char add_grp
(
  group_table *key_file,
  const char *new_grp
);

static group *alloc_group
(
  void
);

static key_value_pair_list *alloc_key_value_pair
(
  void
);

static char add_key_value_pair
(
  group_table *key_file,
  const char *cur_grp,
  const char *key,
  const char *val
);


//Definitions
void free_strs
(
  char **str_array
)
{
  char **str_array_cpy = str_array;
  if(str_array != NULL) {
     while(*str_array != NULL) {
           free(*str_array);
           str_array++;
     }
  }
  free(str_array_cpy);
}
//ToDo: Come up with code hashing
//function
unsigned int get_hash_code
(
  const char *str
)
{

  unsigned len = strlen(str);
  unsigned int i;
  unsigned int hash_code = 0;

  for(i = 0; len > 0; len--, i++) {
      hash_code += (int)((str[i] * pow(2, len))) % INT_MAX;
      hash_code %= INT_MAX;
  }
  return hash_code;
}

static key_value_pair_list *alloc_key_value_pair
(
  void
)
{
  key_value_pair_list *key_list = NULL;

  key_list = (key_value_pair_list *)malloc(
                                       sizeof(key_value_pair_list));
  if(key_list != NULL) {
     key_list->key = NULL;
     key_list->next = NULL;
     key_list->value = NULL;
  }
  return key_list;
}

static group * alloc_group
(
  void
)
{
  group *grp = NULL;
  unsigned int i;

  grp = (group *)malloc(sizeof(group));
  if(grp != NULL) {
     grp->grp_name = NULL;
     grp->grp_next = NULL;
     grp->num_of_keys = 0;
     grp->keys_hash_size = MAX_UNIQ_KEYS;
     grp->list = (key_value_pair_list **)malloc
                    (sizeof(key_value_pair_list *) * grp->keys_hash_size);
     if(grp->list == NULL) {
        ALOGE("Could not alloc group\n");
        free(grp);
        grp = NULL;
     }else {
        for(i = 0; i < grp->keys_hash_size; i++) {
            grp->list[i] = NULL;
        }
     }
  }
  return grp;
}

group_table *get_key_file
(
)
{
  group_table *t = NULL;
  unsigned int i;

  t = (group_table *)malloc(sizeof(group_table));
  if (t != NULL) {
      t->grps_hash_size = MAX_UNIQ_GRPS;
      t->num_of_grps = 0;
      t->grps_hash = (group **)malloc(sizeof(group *)
                                       * t->grps_hash_size);
      if (t->grps_hash == NULL) {
          free(t);
          return NULL;
      }
      for(i = 0; i < t->grps_hash_size; i++) {
          t->grps_hash[i] = NULL;
      }
  }
  return t;
}

void free_key_file(
  group_table *key_file
)
{
  unsigned int i;

  if(key_file != NULL) {
     if(key_file->grps_hash != NULL) {
        for(i = 0; i < key_file->grps_hash_size; i++) {
            free_grp_list(key_file->grps_hash[i]);
        }
     }
     free(key_file->grps_hash);
     free(key_file);
  }
}

static void free_grp_list
(
  group *a
)
{
  group *next;
  unsigned int i;

  while(a != NULL) {
       next = a->grp_next;
       if(a->list != NULL) {
          for(i = 0; i < a->keys_hash_size; i++) {
              free_key_list(a->list[i]);
          }
       }
       free(a->grp_name);
       free(a->list);
       free(a);
       a = next;
  }
}

static void free_key_list
(
  key_value_pair_list *a
)
{
  key_value_pair_list *next;

  while(a != NULL) {
       next = a->next;
       free(a->key);
       free(a->value);
       free(a);
       a = next;
  }
}
//return all the groups
//present in the file
char **get_grps
(
  const group_table *key_file
)
{
  char **grps = NULL;
  unsigned int i = 0;
  unsigned int j = 0;
  unsigned int grp_len;
  group *grp_list;

  if((key_file == NULL)
     || (key_file->grps_hash == NULL)
     || (key_file->grps_hash_size == 0)
     || (key_file->num_of_grps == 0)) {
     return grps;
  }
  grps = (char **)calloc((key_file->num_of_grps + 1),
                           sizeof(char *));
  if(grps == NULL) {
     return grps;
  }
  for(i = 0; i < key_file->grps_hash_size; i++) {
      grp_list = key_file->grps_hash[i];
      while(grp_list != NULL) {
            grp_len = strlen(grp_list->grp_name);
            grps[j] = (char *)malloc(sizeof(char) *
                                     (grp_len + 1));
            if(grps[j] == NULL) {
               free_strs(grps);
               grps = NULL;
               return grps;
            }
            memcpy(grps[j], grp_list->grp_name,
                   (grp_len + 1));
            grp_list = grp_list->grp_next;
            j++;
      }
  }
  grps[j] = NULL;
  return grps;
}

//returns the list of keys
//associated with group name
char **get_keys
(
  const group_table *key_file,
  const char *grp_name
)
{
  unsigned int grp_hash_code;
  unsigned int grp_index;
  unsigned int num_of_keys;
  unsigned int i;
  unsigned int j = 0;
  unsigned int key_len;
  group *grp;
  key_value_pair_list *key_val_list;
  char **keys = NULL;

  if((key_file == NULL) || (grp_name == NULL)
     || (key_file->num_of_grps == 0) ||
     (key_file->grps_hash_size == 0) ||
     (key_file->grps_hash == NULL) ||
     (!strcmp(grp_name, ""))) {
      return keys;
  }
  grp_hash_code = get_hash_code(grp_name);
  grp_index = (grp_hash_code % key_file->grps_hash_size);
  grp = key_file->grps_hash[grp_index];
  while(grp != NULL) {
        if(!strcmp(grp_name, grp->grp_name)) {
            if((grp->num_of_keys == 0)
               || (grp->keys_hash_size == 0)
               || (grp->list == 0)) {
               return keys;
            }
            keys = (char **)calloc((grp->num_of_keys + 1),
                                   sizeof(char *));
            if(keys == NULL) {
                return keys;
            }
            for(i = 0; i < grp->keys_hash_size; i++) {
                key_val_list = grp->list[i];
                while(key_val_list != NULL) {
                     key_len = strlen(key_val_list->key);
                     keys[j] = (char *)malloc(sizeof(char) *
                                              (key_len + 1));
                     if(keys[j] == NULL) {
                         free_strs(keys);
                         keys = NULL;
                         return keys;
                     }
                     memcpy(keys[j], key_val_list->key,
                            (key_len + 1));
                     j++;
                     key_val_list = key_val_list->next;
                }
            }
            keys[j] = NULL;
            return keys;
        }
        grp = grp->grp_next;
  }
  return keys;
}

char *get_value
(
   const group_table *key_file,
   const char *grp_name,
   const char *key
)
{
   unsigned int grp_hash_code;
   unsigned int key_hash_code;
   unsigned int grp_index;
   unsigned int key_index;
   unsigned val_len;
   char *val = NULL;
   group *grp;
   key_value_pair_list *list;

   if((key_file == NULL) || (grp_name == NULL)
      || (key == NULL) || (key_file->grps_hash == NULL)
      || (key_file->grps_hash_size == 0) || !strcmp(grp_name, "")
      ||(!strcmp(key, ""))) {
       return NULL;
   }
   grp_hash_code = get_hash_code(grp_name);
   key_hash_code = get_hash_code(key);
   grp_index = (grp_hash_code % key_file->grps_hash_size);
   grp = key_file->grps_hash[grp_index];
   while(grp != NULL) {
         if(!strcmp(grp_name, grp->grp_name) && grp->keys_hash_size
            && grp->list) {
            key_index = (key_hash_code % grp->keys_hash_size);
            list = grp->list[key_index];
            while((list != NULL) && (strcmp(list->key, key))) {
                   list = list->next;
            }
            if(list != NULL) {
                val_len = strlen(list->value);
                val = (char *)malloc(sizeof(char) * (val_len + 1));
                if(val != NULL) {
                   memcpy(val, list->value, val_len);
                   val[val_len] = '\0';
                }
            }
            return val;
         }
         grp = grp->grp_next;
   }
   return val;
}
//open the file,
//read, parse and load
//returns PARSE_SUCCESS if successfully
//loaded else PARSE_FAILED
char parse_load_file
(
  group_table *key_file,
  const char *file
)
{
  FILE *fp;
  char ret = FALSE;

  if((file == NULL) || !strcmp(file, "")) {
     ALOGE("File name is null or empty \n");
     return ret;
  }

  fp = fopen(file, "r");
  if(fp == NULL) {
     ALOGE("could not open file for read\n");
     return ret;
  }

  ret = parse_load_frm_fhandler(key_file, fp);
  fclose(fp);

  return ret;
}

//Read block of data from file handler
//extract each line, check kind of line(comment,
//group, key value pair)
static char parse_load_frm_fhandler
(
  group_table *key_file,
  FILE *fp
)
{
  char buf[MAX_LINE_LEN];
  char ret = TRUE;
  char *line = NULL;
  void *new_line;
  char *cur_grp = NULL;
  unsigned line_len = 0;
  unsigned line_allocated = 0;
  unsigned int bytes_read = 0;
  unsigned int i;
  bool has_carriage_rtn = false;

  while((bytes_read = fread(buf, 1, MAX_LINE_LEN, fp))) {
        for(i = 0; i < bytes_read; i++) {
            if(line_len == line_allocated) {
                line_allocated += 25;
                new_line = realloc(line, line_allocated);
                if(new_line == NULL) {
                   ret = FALSE;
                   ALOGE("memory allocation failed for line\n");
                   break;
                }
                line = (char *)new_line;
            }
            if((buf[i] == '\n')) {
                has_carriage_rtn = false;
                line[line_len] = '\0';
                ret = parse_line(key_file, line, &cur_grp);
                line_len = 0;
                if(ret == FALSE) {
                   ALOGE("could not parse the line, line not proper\n");
                   break;
                }
            }else if(buf[i] == '\r') {
                ALOGE("File has carriage return\n");
                has_carriage_rtn = true;
            }else if(has_carriage_rtn) {
                ALOGE("File format is not proper, no line character\
                        after carraige return\n");
                ret = FALSE;
                break;
            }else {
                line[line_len] = buf[i];
                line_len++;
            }
        }
        if (!ret) {
            break;
        }
  }
  free(line);
  free(cur_grp);

  return ret;
}

//checks whether a line is
//comment or grp or key pair value
//and accordingly adds to list
static char parse_line
(
  group_table *key_file,
  const char *line,
  char **cur_grp
)
{
  const char *line_begin;
  char *grp_name;
  unsigned int len;

  if((line == NULL) || (key_file == NULL)) {
      ALOGE("key file or line is null\n");
      return FALSE;
  }

  for(line_begin = line; isspace(*line_begin);
          line_begin++);

  if(line_is_comment(line_begin)) {
      ALOGE("line is comment\n");
      return TRUE;
  }else if(line_is_grp(key_file, line_begin, cur_grp)) {
      ALOGE("line is grp\n");
      return TRUE;
  }else if(line_is_key_value_pair(key_file, line_begin, *cur_grp)) {
      ALOGE("line is key value pair\n");
      return TRUE;
  }else {
     ALOGE("line is neither comment, grp nor key value pair\n");
     return FALSE;
  }
}

static char line_is_comment
(
  const char *str
)
{
  if(str == NULL) {
      return FALSE;
  }else if(((*str) == '#') || ((*str) == '\0')
       || ((*str) == '\n')) {
      return TRUE;
  }else {
      ALOGE("line is not comment\n");
      return FALSE;
  }
}

//return true if a group
//name already exist
//else false
static char grp_exist
(
  const group_table *key_file,
  const char *new_grp
)
{
  unsigned hash_code;
  unsigned int index;
  group *grp;

  if((key_file == NULL) || (new_grp == NULL)
     || (!key_file->grps_hash_size)) {
     return FALSE;
  }else {
    hash_code = get_hash_code(new_grp);
    index = hash_code % key_file->grps_hash_size;
    grp = key_file->grps_hash[index];
    while(grp != NULL) {
          if (!strcmp(grp->grp_name, new_grp))
              return TRUE;
          grp = grp->grp_next;
    }
    return FALSE;
  }
}

//Add a group to group
//table if it does not exist
static char add_grp
(
  group_table *key_file,
  const char *new_grp
)
{
  unsigned int hash_code;
  unsigned int index;
  unsigned int grp_name_len;
  group *grp;

  if(!grp_exist(key_file, new_grp)) {
      if((key_file == NULL) || (new_grp == NULL)
         || !key_file->grps_hash_size) {
         return FALSE;
      }
      hash_code = get_hash_code(new_grp);
      ALOGE("group hash code is: %u\n", hash_code);
      index = hash_code % key_file->grps_hash_size;
      ALOGE("group index is: %u\n", index);
      grp = alloc_group();
      if(grp == NULL) {
         return FALSE;
      }
      grp_name_len = strlen(new_grp);
      grp->grp_name = (char *)malloc(
                                  sizeof(char) * (grp_name_len + 1));
      if(grp->grp_name == NULL) {
         ALOGE("could not alloc memory for group name\n");
         ALOGE("Add group failed\n");
         free_grp_list(grp);
         return FALSE;
      }else {
         memcpy(grp->grp_name, new_grp, (grp_name_len + 1));
      }
      grp->grp_next = key_file->grps_hash[index];
      key_file->grps_hash[index] = grp;
      key_file->num_of_grps++;
      return TRUE;
  }else {
     return FALSE;
  }
}

//checks validity of a group
//a valid group is
//inside [] group name must be
//alphanumeric
//Example: [grpName]
static char line_is_grp
(
  group_table *key_file,
  const char *str,
  char **cur_grp
)
{
  const char *g_start;
  const char *g_end;
  char *new_grp;
  unsigned int grp_len;

  if ((str == NULL) || (key_file == NULL)) {
      ALOGE("str is null or key file is null\n");
      return FALSE;
  }
  //checks start mark char ']'
  if(((*str) != '[')) {
      ALOGE("start mark is not '['\n");
      return FALSE;
  }else {
      str++;
      g_start = str;
  }
  //checks the end char '['
  while((*str != '\0') && ((*str) != ']')) {
        str++;
  }
  //if end mark group not found
  if ((*str) != ']') {
       ALOGE("grp end mark is not '['\n");
       return FALSE;
  }else {
       g_end = (str - 1);
  }

  str++;
  //if end mark found checks the rest chars as well
  //rest chars should be space
  while(((*str) == ' ') || ((*str) == '\t')) {
        str++;
  }
  if(*str) {
     ALOGE("after ']' there are some character\n");
     return FALSE;
  }

  str = g_start;
  while((*g_start != '\0') && (g_start != g_end)
         && isalnum(*g_start)) {
        g_start++;
  }
  if((g_start == g_end) && isalnum(*g_start)) {
      //look up if already exist
      //return false else insert the grp in grp table
      grp_len = (g_end - str + 1);
      new_grp = (char *)malloc(sizeof(char) * (grp_len + 1));
      if (new_grp == NULL) {
          ALOGE("could not alloc memory for new group\n");
          return FALSE;
      }
      memcpy(new_grp, str, grp_len);
      new_grp[grp_len] = '\0';

      if(add_grp(key_file, new_grp)) {
          free(*cur_grp);
         *cur_grp = new_grp;
         return TRUE;
      }else {
         ALOGE("could not add group to group table\n");
         return FALSE;
      }
  }else {
      return FALSE;
  }
}

static char key_exist
(
  const group_table *key_file,
  const char *cur_grp,
  const char *key
)
{
  unsigned int grp_hash_code;
  unsigned int key_hash_code;
  unsigned int grp_index;
  unsigned int key_index;
  group *grp = NULL;
  key_value_pair_list *list = NULL;

  if((key_file != NULL) && (cur_grp != NULL)
      && (key != NULL) && ((key_file->grps_hash != NULL))
      && (strcmp(key, ""))) {
     grp_hash_code = get_hash_code(cur_grp);
     grp_index = (grp_hash_code % key_file->grps_hash_size);
     grp = key_file->grps_hash[grp_index];
     key_hash_code = get_hash_code(key);
     while((grp != NULL)) {
           if(!strcmp(cur_grp, grp->grp_name)) {
              key_index = (key_hash_code % grp->keys_hash_size);
              if(grp->list)
                 list = grp->list[key_index];
              while((list != NULL) && strcmp(key, list->key)) {
                    list = list->next;
              }
              if(list != NULL){
                  return TRUE;
              }else{
                  return FALSE;
              }
           }
           grp = grp->grp_next;
     }
     if(!grp) {
        return TRUE;
     }else {
        return FALSE;
     }
  }else {
     return FALSE;
  }
}

//checks validity of key
//a valid key must start in
//a seperate line and key must
//be alphanumeric and before '='
//there must not be any space
//Example: key=value
static char line_is_key_value_pair
(
  group_table *key_file,
  const char *str,
  const char *cur_grp
)
{
  const char *equal_start;
  char *key;
  char *val;
  unsigned key_len;
  unsigned val_len;

  if((str == NULL) || (cur_grp == NULL) ||
     !strcmp(cur_grp, "") || (key_file == NULL)) {
     ALOGE("line is null or cur group or key file is null or empty\n");
     return FALSE;
  }
  equal_start = strchr(str, '=');
  key_len = (equal_start - str);
  if((equal_start == NULL) || (equal_start == str)) {
     ALOGE("line does not have '=' character or no key\n");
     return FALSE;
  }
  while((str != equal_start) && isalnum(*str))
        str++;
  if((str == equal_start)) {
      key = (char *)malloc(sizeof(char) * (key_len + 1));
      if(key == NULL) {
         ALOGE("could not alloc memory for new key\n");
         return FALSE;
      }
      equal_start++;
      val_len = strlen(equal_start);
      val = (char *)malloc(sizeof(char) * (val_len + 1));
      if(val == NULL) {
         ALOGE("could not alloc memory for value\n");
         return FALSE;
      }
      memcpy(key, (str - key_len), key_len);
      memcpy(val, equal_start, val_len);
      key[key_len] = '\0';
      val[val_len] = '\0';
      ALOGE("Grp: %s, key: %s, value: %s\n", cur_grp, key, val);
      return add_key_value_pair(key_file,
                                 cur_grp, key, val);
  }else {
     ALOGE("key name doesnot have alpha numeric char\n");
     return FALSE;
  }
}

static char add_key_value_pair
(
  group_table *key_file,
  const char *cur_grp,
  const char *key,
  const char *val
)
{
  unsigned int grp_hash_code;
  unsigned int key_hash_code;
  unsigned int grp_index;
  unsigned int key_index;
  unsigned key_len, val_len;
  group *grp = NULL;
  key_value_pair_list *list = NULL;

  if((key_file != NULL) && (cur_grp != NULL)
      && (key != NULL) && ((key_file->grps_hash != NULL))
      && (strcmp(key, ""))) {
     grp_hash_code = get_hash_code(cur_grp);
     ALOGE("grp hash code is %u\n", grp_hash_code);
     grp_index = (grp_hash_code % key_file->grps_hash_size);
     ALOGE("grp index is %u\n", grp_index);
     grp = key_file->grps_hash[grp_index];
     key_hash_code = get_hash_code(key);
     while((grp != NULL)) {
           if(!strcmp(cur_grp, grp->grp_name)) {
              key_index = (key_hash_code % grp->keys_hash_size);
              if(grp->list) {
                 list = grp->list[key_index];
              }else {
                 ALOGE("group list is null\n");
                 return FALSE;
              }
              while((list != NULL) && strcmp(key, list->key)) {
                    list = list->next;
              }
              if(list != NULL) {
                  ALOGE("group already contains the key\n");
                  return FALSE;
              }else{
                  list = alloc_key_value_pair();
                  if(list == NULL) {
                     ALOGE("add key value failed as could not alloc memory for key\
                            val pair\n");
                     return FALSE;
                  }
                  key_len = strlen(key);
                  list->key = (char *)malloc(sizeof(char) *
                                              (key_len + 1));
                  if(list->key == NULL) {
                     ALOGE("could not alloc memory for key\n");
                     free(list);
                     return FALSE;
                  }
                  val_len = strlen(val);
                  list->value = (char *)malloc(sizeof(char) *
                                                (val_len + 1));
                  if(!list->value) {
                      free(list->key);
                      free(list);
                      return FALSE;
                  }
                  memcpy(list->key, key, key_len);
                  memcpy(list->value, val, val_len);
                  list->key[key_len] = '\0';
                  list->value[val_len] = '\0';
                  list->next = grp->list[key_index];
                  grp->list[key_index] = list;
                  grp->num_of_keys++;
                  return TRUE;
              }
           }
           grp = grp->grp_next;
     }
     ALOGE("group does not exist\n");
     return FALSE;
  }else {
     return FALSE;
  }
}
